(function($) {

    var Layer = function(element, options) {
        this.type = null;
        this.$element = null;
        this.options = null;
        this.layers = null;
        this.layer = null;
        this.container = null;
        this.init('layer', element, options);
    };

    Layer.DEFAULT = {
        container: null,
        cancelSelector: null,
        okSelector: null
    };

    Layer.prototype.init = function(type, element, options) {
        this.type = type;
        this.$element = $(element);
        this.options = this.getOptions(options);
        this.layers = [];
        this.layer = null;

        var _this = this;
        this.setContainer(this.options.container || this.$element);
        if (this.options.cancelSelector) {
            this.$element
                .on("click", this.options.cancelSelector, function() {
                    var $layer = $("*[layer-name=" + _this.layer.name + "]", _this.container);
                    _this.layer.close && _this.layer.close.call($layer);
                    remove.call(_this);
                });
        }
        if (this.options.okSelector) {
            this.$element
                .on("click", this.options.okSelector, function() {
                    var $layer = $("*[layer-name=" + _this.layer.name + "]", _this.container);
                    // 若存在验证器，则先验证
                    if (!_this.layer.validator || _this.layer.validator.call(_this, _this.layer.value)) {
                        // 若存在处理函数，则先调用处理函数
                        if (!_this.layer.handler || _this.layer.handler.call($layer, _this.layer.value)) {
                            remove.call(_this);
                        }
                    }
                });
        }
    };

    Layer.prototype.getOptions = function(options) {
        return $.extend({}, Layer.DEFAULTS, this.$element.data(), options);
    };

    Layer.prototype.push = function(layer) {
        this.layers.push(layer);
        this.layer = layer;
    };

    Layer.prototype.pop = function() {
        delete this.layers.pop();
        this.layer = this.layers.slice(-1)[0];
    };

    Layer.prototype.render = function(scope) {
        var layer = this.layer;

        layer || this.push((layer = { name: 'root', root: true }));
        scope && (layer.scope = $.extend(layer.scope || {}, scope));

        render.call(this);
    };

    Layer.prototype.setContainer = function(container) {
        this.container = container || null;
    };

    Layer.prototype.scope = function() {
        return this.layers.reduce(function(scope, layer) {
            layer.scope && (scope = $.extend(scope, layer.scope));
            return scope;
        }, {});
    };

    Layer.prototype.value = function(value) {
        this.layer.value = value;
    };

    Layer.prototype.reset = function() {
        this.layers = [];
        this.layer = null;
    };

    Layer.prototype.validator = function(validator) {
        this.layer.validator = validator;
    };

    function render() {
        var layer = this.layer, mixed = this.scope(), eventName = 'show.' + this.type, e = $.Event(eventName);

        var $layer = $("*[layer-name=" + this.layer.name + "]", this.container);

        if (!$layer.is(":visible")) {
            $layer.one(eventName, function (e) { e.stopPropagation(); })
                .show()
                .trigger(e);
        }

        layer.overlap || $("*[layer-name]", this.container).not("*[layer-name=" + this.layer.name + "]").hide();

        var container = layer.overlap ? $layer : this.container;
        $("*[include-if]", container).each(function () {
            if (!execute(layer, mixed || {}, $(this).attr("include-if"))) {
                $(this).remove();
            }
        });

        // update the show-if directive
        $("*[show-if]", container).each(function () {
            showIf.call(this, $(this).attr("show-if"));
        });

        // global show-if directive which under this.$element, not under this.container.
        $("*[g-show-if]", this.$element).each(function () {
            showIf.call(this, $(this).attr("g-show-if"));
        });

        function showIf(expression) {
            if (execute(layer, mixed || {}, expression)) {
                $(this)
                    .one(eventName, function(e) { e.stopPropagation(); })
                    .show()
                    .trigger(e);
            } else {
                $(this).hide();
            }
        }
    }

    function remove() {
        $("*[layer-name=" + this.layer.name + "]", this.container).hide();
        this.pop();
        this.render();
    }

    function execute(layer, scope, expression) {
        return eval("(function(layer, scope) { \
                return " + expression + "; \
            })(layer, scope);");
    }

    function Plugin(option) {
        var $this   = $(this),
            data    = $this.data('layer'),
            options = typeof option === 'object' && option;

        if (!data) {
            $this.data('layer', (data = new Layer(this, options)));
        }

        if (typeof option === 'string') {
            data[option].apply(data, Array.prototype.slice.call(arguments, 1));
        }

        if (arguments.length === 0) {
            return data;
        }

        return this;
    }

    $.fn.layer             = Plugin;
    $.fn.layer.constructor = Layer;

})(jQuery);